package redis

//  相关操作指令
//  涉及到的数据结构有  String List Set Hash SortedSet

import (
	"fmt"
	"github.com/pkg/errors"
	"time"

	"github.com/garyburd/redigo/redis"
)

const (
	MaxIdle          = 3
	IdleTimeoutSec   = 240
	DialReadTimeout  = 60 * time.Second
	DialWriteTimeout = 10 * time.Second
)

var Conn *redis.Pool

// NewPool returns a new  RedisConnection pool.
func InitPool(URL, Password string) error {
	Conn = &redis.Pool{
		MaxIdle:     MaxIdle,
		IdleTimeout: IdleTimeoutSec * time.Second,
		Dial: func() (redis.Conn, error) {
			c, err := redis.Dial("tcp", URL,
				redis.DialReadTimeout(DialReadTimeout),
				redis.DialWriteTimeout(DialWriteTimeout),
				redis.DialPassword(Password),
			)
			if err != nil {
				return nil, fmt.Errorf(" RedisConnection error: %s", err)
			}
			return c, err
		},
		TestOnBorrow: func(c redis.Conn, t time.Time) error {
			_, err := c.Do("PING")
			if err != nil {
				return fmt.Errorf("ping  error: %s", err)
			}
			return nil
		},
	}
	return nil
}

/**
+----------------------------------------------------------
* 通用操作
+----------------------------------------------------------
* @access public
+----------------------------------------------------------
* @function 	Expire   设置过期时间
* @function 	Exists   检查key是否存在
* @function 	Delete   删除key对应是数据
+----------------------------------------------------------
*/

// 设置过期时间
func Expire(k string, time int) (bool, error) {
	c := Conn.Get()
	defer c.Close()
	reply, err := redis.Bool(c.Do("EXPIRE", k, time))
	if err != nil {
		return false, err
	}
	return reply, nil
}

// 检查key是否存在
func Exists(k string) bool {
	c := Conn.Get()
	defer c.Close()
	reply, err := redis.Bool(c.Do("EXISTS", k))
	if err != nil {
		return false
	}
	return reply
}

// 删除key对应是数据
func Delete(k string) (reply interface{}, err error) {
	c := Conn.Get()
	defer c.Close()
	return c.Do("DEL", k)
}

// 删除key对应是数据
func UnDelete(k string) {
	c := Conn.Get()
	defer c.Close()
	_, _ = c.Do("DEL", k)
	return
}

// 获取缓存的过期时间
func RedisPttl(k string) (expire int, err error) {
	c := Conn.Get()
	defer c.Close()
	expire, err = redis.Int(c.Do("PTTL", k))
	if err != nil {
		return expire, err
	}
	return expire, err
}

//加锁,   time为加锁的时间
//Auth: Xu
func RedisSetnx(k string, value interface{}, time int) (bool, error) {
	c := Conn.Get()
	defer c.Close()
	reply, err := redis.String(c.Do("SET", k, value, "EX", time, "NX"))
	if reply != "OK" && err != nil {
		return false, err
	}
	return true, nil
}

/**
+----------------------------------------------------------
* 字符串 String
+----------------------------------------------------------
* @access public
+----------------------------------------------------------
* @function 	Set   		将字符串值 value 关联到 key
* @function 	Get   		返回 key 所关联的字符串值
* @function 	GetList   	返回 key 所关联的字符串值为 字符串数组
* @function 	Incr   	string类型缓存自增1
+----------------------------------------------------------
*/

// 将字符串值 value 关联到 key
func Set(k string, value interface{}, time int) (bool, error) {
	c := Conn.Get()
	defer c.Close()
	if time >= 0 {
		str, err := redis.String(c.Do("SETEX", k, time, value))
		if err != nil && str != "OK" {
			return false, err
		}
	} else {
		str, err := redis.String(c.Do("SET", k, value))
		if str != "OK" && err != nil {
			return false, err
		}
	}
	return true, nil
}

func SetUnRe(k string, value interface{}, time int) {
	c := Conn.Get()
	defer c.Close()
	if time >= 0 {
		_, _ = redis.String(c.Do("SETEX", k, time, value))
	} else {
		_, _ = redis.String(c.Do("SET", k, value))
	}
}

// 返回 key 所关联的字符串值
func Get(k string) (string, error) {
	c := Conn.Get()
	defer c.Close()
	reply, err := redis.String(c.Do("GET", k))
	if err != nil {
		return "", err
	}
	return reply, nil
}

// 返回 key 所关联的字符串值为 字符串数组
func GetList(k string) ([]byte, error) {
	c := Conn.Get()
	defer c.Close()
	reply, err := redis.Bytes(c.Do("GET", k))
	if err != nil {
		return nil, err
	}
	return reply, nil
}

// string类型缓存自增1
func Incr(k string) (int, error) {
	c := Conn.Get()
	defer c.Close()
	reply, err := redis.Int(c.Do("INCR", k))
	if err != nil {
		return 0, err
	}
	return reply, nil
}

// string类型缓存自减1
func Decr(k string) (int, error) {
	c := Conn.Get()
	defer c.Close()
	reply, err := redis.Int(c.Do("DECR", k))
	if err != nil {
		return 0, err
	}
	return reply, nil
}

// 自增指定数量
func Incrby(k string, value int) (int, error) {
	c := Conn.Get()
	defer c.Close()
	reply, err := redis.Int(c.Do("INCRBY", k, value))
	if err != nil {
		return 0, err
	}
	return reply, nil
}

// 自增指定浮点型数值
func IncrByFloat(k string, value float64) (float64, error) {
	c := Conn.Get()
	defer c.Close()
	reply, err := redis.Float64(c.Do("INCRBYFLOAT", k, value))
	if err != nil {
		return 0, err
	}
	return reply, nil
}

/**
+----------------------------------------------------------
* 列表 List
+----------------------------------------------------------
* @access public
+----------------------------------------------------------
* @function 	LPush   命令将一个值插入到列表头部
* @function 	RPush   在列表中添加一个值
* @function 	LPop    移除并返回列表的第一个元素
* @function 	LLen    获取列表的长度
* @function 	LRem    移除列表中与参数 VALUE 相等的元素
+----------------------------------------------------------
*/

// 命令将一个值插入到列表头部
func LPush(k string, value interface{}) (int, error) {
	c := Conn.Get()
	defer c.Close()
	reply, err := redis.Int(c.Do("LPUSH", k, value))
	if err != nil {
		return 0, err
	}
	return reply, nil
}

// 命令将一个值插入到列表头部
func UnLPush(k string, value interface{}) {
	c := Conn.Get()
	defer c.Close()
	_, err := redis.Int(c.Do("LPUSH", k, value))
	if err != nil {
		return
	}
	return
}

// 在列表中添加一个值
func RPush(k string, value interface{}) (int, error) {
	c := Conn.Get()
	defer c.Close()
	reply, err := redis.Int(c.Do("RPUSH", k, value))
	if err != nil {
		return 0, err
	}
	return reply, nil
}

// 移除并返回列表的第一个元素
func LPop(k string) (string, error) {
	c := Conn.Get()
	defer c.Close()
	reply, err := redis.String(c.Do("LPOP", k))
	if err != nil {
		return "", err
	}
	return reply, nil
}

// 获取列表的长度
func LLen(k string) (int, error) {
	c := Conn.Get()
	defer c.Close()
	reply, err := redis.Int(c.Do("LLEN", k))
	if err != nil {
		return 0, err
	}
	return reply, nil
}

// 移除列表中与参数 VALUE 相等的元素
func LRem(k string, count int, value interface{}) (int, error) {
	c := Conn.Get()
	defer c.Close()
	reply, err := redis.Int(c.Do("LREM", k, count, value))
	if err != nil {
		return 0, err
	}
	return reply, nil
}

/**
+----------------------------------------------------------
* 集合 set
+----------------------------------------------------------
* @access public
+----------------------------------------------------------
* @function 	IsMember   	判断 member 元素是否集合 key 的成员
* @function 	SAdd   		在列表中添加一个值
* @function 	SMembers    	返回集合 key 中的所有成员
* @function 	SRem	    	移除集合 key 中所有成员
+----------------------------------------------------------
*/

//	如果 member 元素是集合的成员，返回 1
//	如果 member 元素不是集合的成员，或 key 不存在，返回 0
func IsMember(k, member string) bool {
	c := Conn.Get()
	defer c.Close()
	reply, err := redis.Int(c.Do("SISMEMBER", k, member))
	if err == nil && reply == 1 {
		return true
	}
	return false
}

// 在列表中添加一个值
func SAdd(k, v string) (bool, error) {
	c := Conn.Get()
	defer c.Close()
	reply, err := redis.Bool(c.Do("SADD", k, v))
	if err != nil {
		return false, err
	}
	return reply, nil
}

// 返回集合 key 中的所有成员
func SMembers(k string) ([]string, bool) {
	c := Conn.Get()
	defer c.Close()
	reply, err := redis.Strings(c.Do("SMEMBERS", k))
	if err != nil {
		return nil, false
	}
	return reply, true
}

// 返回集合 key 中的成员数
func SCard(k string) (int, error) {
	c := Conn.Get()
	defer c.Close()
	reply, err := redis.Int(c.Do("SCARD", k))
	if err != nil {
		return 0, err
	}
	return reply, nil
}

// 移除集合 key 中的一个或多个 member 元素，不存在的 member 元素会被忽略。
// 当 key 不是集合类型，返回一个错误。
func SRem(k, v string) (bool, error) {
	c := Conn.Get()
	defer c.Close()
	reply, err := redis.Bool(c.Do("SREM", k, v))
	if err == nil || reply == true {
		return true, nil
	}
	return false, err
}

/**
+----------------------------------------------------------
* 哈希 Hash
+----------------------------------------------------------
* @access public
+----------------------------------------------------------
* @function 	HSet   	添加元素到集合
* @function 	HGetAll   	以列表形式返回哈希表的域和域的值  若 key 不存在，返回空列表
* @function 	HGet    	获取集合中的元素
+----------------------------------------------------------
*/

// 获取集合中的元素
func HExists(k, field string) (bool, error) {
	c := Conn.Get()
	defer c.Close()
	result, err := redis.Int(c.Do("HEXISTS", k, field))
	if err != nil || result == 0 {
		return false, err
	}
	return true, nil
}

// 如果 field 是哈希表中的一个新建域，并且值设置成功，返回 1
// 如果哈希表中域 field 已经存在且旧值已被新值覆盖，返回 0
func HSet(hash string, data map[string]interface{}, time int) (bool, error) {
	c := Conn.Get()
	defer c.Close()

	for key, value := range data {
		_, err := redis.Bool(c.Do("hset", hash, key, value))
		if err != nil {
			return false, err
		}
	}
	if time >= 0 {
		c.Do("EXPIRE", hash, time)
	} else {
		c.Do("PERSIST", hash)
	}

	return true, nil
}

// 以列表形式返回哈希表的域和域的值  若 key 不存在，返回空列表。
func HGetAll(hash string) (map[string]interface{}, error) {
	c := Conn.Get()
	defer c.Close()
	if !Exists(hash) {
		return nil, errors.New("HASH缓存不存在")
	}
	resultMap := make(map[string]interface{})
	result, err := redis.Values(c.Do("hgetall", hash))
	if err != nil {
		return nil, err
	} else {

		for index, v := range result {
			if index%2 != 0 {
				keyIndex := index - 1
				stringV := string(v.([]byte))
				key := string(result[keyIndex].([]byte))
				resultMap[key] = stringV
			}
		}
	}

	return resultMap, nil
}

// 获取集合中的元素
func HGet(k, field string) (string, error) {
	c := Conn.Get()
	defer c.Close()

	result, err := redis.String(c.Do("HGet", k, field))
	if err != nil {
		return "", err
	}
	return result, nil
}

// 删除指定key
func HDel(k, field string) (bool, error) {
	c := Conn.Get()
	defer c.Close()

	result, err := redis.Bool(c.Do("HDel", k, field))
	if err != nil {
		return false, err
	}
	return result, nil
}

/**
+----------------------------------------------------------
* 有序集合 SortedSet
+----------------------------------------------------------
* @access public
+----------------------------------------------------------
* @function 	ZAdd   	添加元素到有序集合
* @function 	ZRem   	移除有序集合中的元素
* @function 	ZCard    	查询有序集合的成员数
* @function 	ZRange    	通过索引区间返回有序集合成指定区间内的成员
* @function 	ZScore    	返回有序集 key 中，成员 member 的 score 值
+----------------------------------------------------------
*/

// 判断有序集中中是否有此成员
func ZIsMember(k, v string) (bool, error) {
	c := Conn.Get()
	defer c.Close()
	reply, err := redis.Int(c.Do("ZRANK", k, v))
	if err != nil || reply < 0 {
		return false, err
	}
	return true, nil
}

// 添加元素到有序集合
func ZAdd(k, v string, score int64) (int, error) {
	c := Conn.Get()
	defer c.Close()
	reply, err := redis.Int(c.Do("ZADD", k, score, v))
	if err != nil {
		return 0, err
	}
	return reply, nil
}

// 移除有序集合中的元素
func ZRem(k string, value string) (int, error) {
	c := Conn.Get()
	defer c.Close()
	reply, err := redis.Int(c.Do("ZREM", k, value))
	if err != nil {
		return 0, err
	}
	return reply, nil
}

// 查询有序集合的成员数
func ZCard(k string) (int, error) {
	c := Conn.Get()
	defer c.Close()
	reply, err := redis.Int(c.Do("ZCARD", k))
	if err != nil {
		return 0, err
	}
	return reply, nil
}

// 通过索引区间返回有序集合成指定区间内的成员
func ZRange(k string, start, stop int) ([]string, error) {
	c := Conn.Get()
	defer c.Close()
	reply, err := redis.Strings(c.Do("ZRANGE", k, start, stop))
	if err != nil {
		return reply, err
	}
	return reply, nil
}

// 返回有序集 key 中，成员 member 的 score 值
func ZScore(k, v string) (int, error) {
	c := Conn.Get()
	defer c.Close()
	reply, err := redis.Int(c.Do("ZSCORE", k, v))
	if err != nil {
		return 0, err
	}
	return reply, nil
}

// (倒序)通过索引区间返回有序集合成指定区间内的成员
func ZRevRange(k string, start, stop int) ([]string, error) {
	c := Conn.Get()
	defer c.Close()
	reply, err := redis.Strings(c.Do("ZREVRANGE", k, start, stop))
	if err != nil {
		return reply, err
	}
	return reply, nil
}

func ZCount(k string, start, stop int64) (int, error) {
	c := Conn.Get()
	defer c.Close()
	reply, err := redis.Int(c.Do("ZCOUNT", k, start, stop))
	if err != nil {
		return reply, err
	}
	return reply, nil
}

//移除指定排名区间的元素
func ZRemRangeByRank(k string, start, stop int64) (int, error) {
	c := Conn.Get()
	defer c.Close()
	reply, err := redis.Int(c.Do("ZREMRANGEBYRANK", k, start, stop))
	if err != nil {
		return reply, err
	}
	return reply, nil
}

//移除指定SCORE区间的元素
func ZRemRangeBySCORE(k string, start, stop int64) (int, error) {
	c := Conn.Get()
	defer c.Close()
	reply, err := redis.Int(c.Do("ZREMRANGEBYSCORE", k, start, stop))
	if err != nil {
		return reply, err
	}
	return reply, nil
}

func ZRangeByScore(k string, start, stop int64) ([]string, error) {

	c := Conn.Get()
	defer c.Close()
	reply, err := redis.Strings(c.Do("ZRANGEBYSCORE", k, start, stop))
	if err != nil {
		return reply, err
	}
	return reply, nil
}
